/*
 * Copyright (C) 1995, 1997 Wolfgang Solfrank
 * Copyright (c) 1995 Martin Husemann
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *    This product includes software developed by Martin Husemann
 *    and Wolfgang Solfrank.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h>
#include <unistd.h>
#include "fstool_common.h"
#include "fstool_io.h"
#include "fsck_boot.h"

static u_int BOOT_CheckFSI(FSCK_BOOT_SECTOR_S* pstBoot,int fd, u_char** pstFsinfoSector)
{
    u_char* fsinfo_sector = malloc(FSCK_BOOT_MAX_BLOCK_SIZE);
    if(fsinfo_sector == NULL)
    {
        FSTOOL_COMM_LOG_ERROR("-->Invalid FAT32 Extended Parameter! \n");
        return FSCK_FSBOOTERR;
    }
    *pstFsinfoSector = fsinfo_sector;

    if (FSCK_FSOK != FSTOOL_IO_Read(fd, pstBoot->BPB_FSInfo, 1, fsinfo_sector))
    {
        FSTOOL_COMM_LOG_ERROR("Could not read fsinfo! \n");
        return FSCK_READERR;
    }

    if (memcmp(fsinfo_sector, "RRaA", 4)
        || memcmp(fsinfo_sector + FSCK_BOOT_FSI_STR_OFFSET, "rrAa", 4)
        || fsinfo_sector[FSCK_BOOT_FSI_TRA_OFFSET]
        || fsinfo_sector[FSCK_BOOT_FSI_TRA_OFFSET+1]
        || fsinfo_sector[FSCK_BOOT_MAX_BLOCK_SIZE-2]!= 0x55
        || fsinfo_sector[FSCK_BOOT_MAX_BLOCK_SIZE-1]!= 0xaa)
    {
        FSTOOL_COMM_LOG_ERROR("Invalid signature in fsinfo block  need writeover !\n");
        return FSCK_FSBOOTERR;
    }

    pstBoot->FSI_FreeClCnt= fsinfo_sector[FSCK_BOOT_FSI_FREEC_OFFSET] + (fsinfo_sector[FSCK_BOOT_FSI_FREEC_OFFSET+1] << 8)
               + (fsinfo_sector[FSCK_BOOT_FSI_FREEC_OFFSET+2] << 16)
               + (fsinfo_sector[FSCK_BOOT_FSI_FREEC_OFFSET+3] << 24);
    pstBoot->FSI_LastCl= fsinfo_sector[FSCK_BOOT_FSI_NXT_OFFSET] + (fsinfo_sector[FSCK_BOOT_FSI_NXT_OFFSET+1] << 8)
               + (fsinfo_sector[FSCK_BOOT_FSI_NXT_OFFSET+2] << 16)
               + (fsinfo_sector[FSCK_BOOT_FSI_NXT_OFFSET+3] << 24);

    return FSCK_FSOK;
}

u_int FSCK_BOOT_Read(FSCK_BOOT_SECTOR_S* pstBoot, int fd, u_char** pstBootSector, u_char** pstFsinfoSector)
{
    u_int ret = FSCK_FSOK;
    u_char* boot_sector = malloc(FSCK_BOOT_MAX_BLOCK_SIZE);
    if(boot_sector == NULL)
    {
        FSTOOL_COMM_LOG_ERROR("%s:Read bootSector failed due to malloc failed\n", __func__);
        return FSCK_FSFATAL|FSCK_OOM;
    }

    *pstBootSector = boot_sector;

    (void)memset_s(pstBoot, sizeof *pstBoot, 0, sizeof *pstBoot);

    if (FSCK_FSOK != FSTOOL_IO_Read(fd, 0, 1, boot_sector))
    {
        FSTOOL_COMM_LOG_ERROR("could not read pstBoot sector data !\n");
        return FSCK_READERR;
    }
    /**Check the end value    0x 55 aa   **/
    if (boot_sector[FSCK_BOOT_MAX_BLOCK_SIZE-2] != 0x55 || boot_sector[FSCK_BOOT_MAX_BLOCK_SIZE-1] != 0xaa)
    {
        FSTOOL_COMM_LOG_ERROR("Invalid  pstBoot block !\n");
        return FSCK_FSBOOTERR;
    }

    /* Check the OEM Name  */
    FSTOOL_COMM_LOG_INFO("OEM Name[%s]!\n",&boot_sector[FSCK_BOOT_BS_JUMP_SIZE]);
    if (!memcmp(&boot_sector[FSCK_BOOT_BS_JUMP_SIZE], "EXFAT   ", 8))
    {
        FSTOOL_COMM_LOG_ERROR("The exFAT filesystem is not supported now !!\n");
        return FSCK_FS_NOTSUPPORT;
    }

    pstBoot->BPB_BytesPerSec = boot_sector[11] + (boot_sector[12] << 8);
    if ((0 == pstBoot->BPB_BytesPerSec) || (0 != pstBoot->BPB_BytesPerSec % FSCK_BOOT_MAX_BLOCK_SIZE))
    {
        FSTOOL_COMM_LOG_ERROR("Invalid FAT32 Extended Parameter! \n");
        return FSCK_FSBOOTERR;
    }

    pstBoot->BPB_SecPerClus = boot_sector[13];
    if (0 == pstBoot->BPB_SecPerClus)
    {
        FSTOOL_COMM_LOG_ERROR("Invalid FAT32 Extended Parameter! \n");
        return FSCK_FSBOOTERR;
    }

    pstBoot->BPB_RsvdSecCnt = boot_sector[14] + (boot_sector[15] << 8);
    pstBoot->BPB_NumFATs = boot_sector[16];
    if (pstBoot->BPB_NumFATs == 0)
    {
        FSTOOL_COMM_LOG_ERROR("Invalid number of FATs: %u", pstBoot->BPB_NumFATs);
        return FSCK_FSBOOTERR;
    }

    pstBoot->BPB_RootEntCnt = boot_sector[17] + (boot_sector[18] << 8);
    pstBoot->BPB_TotSec16 = boot_sector[19] + (boot_sector[20] << 8);
    /***check the fat type ***/
    if (pstBoot->BPB_RootEntCnt || pstBoot->BPB_TotSec16)
    {
        return FSCK_FSBOOTERR;
    }

    pstBoot->BPB_FATflags = FAT32_FLAG;
    pstBoot->FATClustMask = FSCK_BOOT_CLUSTER_MASK;
    pstBoot->BPB_Media = boot_sector[21];
    pstBoot->BPB_FATSz16 = boot_sector[22] + (boot_sector[23] << 8);
    pstBoot->BPB_SecPerTrk = boot_sector[24] + (boot_sector[25] << 8);
    pstBoot->BPB_NumHeads = boot_sector[26] + (boot_sector[27] << 8);
    pstBoot->BPB_HiddSe = boot_sector[28] + (boot_sector[29] << 8) + (boot_sector[30] << 16) + (boot_sector[31] << 24);
    pstBoot->BPB_TotSec32 = boot_sector[32] + (boot_sector[33] << 8) + (boot_sector[34] << 16) + (boot_sector[35] << 24);
    pstBoot->BPB_FATSz32 = boot_sector[36] + (boot_sector[37] << 8) + (boot_sector[38] << 16) + (boot_sector[39] << 24);

    pstBoot->BPB_ExtFlags=-1;
    if (boot_sector[40] & 0x80)
    {
        pstBoot->BPB_ExtFlags= boot_sector[40] & 0x0f;
    }

    /* check FAT version number only suport 0:0 */
    if (boot_sector[42] || boot_sector[43])
    {
        FSTOOL_COMM_LOG_ERROR("Unknown file system version: %x.%x",boot_sector[42], boot_sector[43]);
        return FSCK_FSBOOTERR;
    }

    /**FAT32 get the Start Clustre of Root Directory   (4 byte) **/
    pstBoot->BPB_RootClus= boot_sector[44] + (boot_sector[45] << 8) + (boot_sector[46] << 16) + (boot_sector[47] << 24);

    /**FAT32 get the FsInfo  sector No(2 byte) **/
    pstBoot->BPB_FSInfo= boot_sector[48] + (boot_sector[49] << 8);
    if (0 == pstBoot->BPB_FSInfo)
    {
        FSTOOL_COMM_LOG_ERROR("Invalid FAT32 Extended Parameter! \n");
        return FSCK_FSBOOTERR;
    }

    ret = BOOT_CheckFSI(pstBoot, fd, pstFsinfoSector);
    if (FSCK_FSOK != ret)
    {
        FSTOOL_COMM_LOG_ERROR("FS Info is invalid! \n");
        return ret;
    }

    /**FAT32 get Backup of Boot sectors No(2 byte) **/
    pstBoot->BPB_BkBootSec= boot_sector[50] + (boot_sector[51] << 8);

    /*mount 1, umount 0*/
    pstBoot->BPB_Reserved1 = boot_sector[65];

    /*****FAT32 ROOTDirEnt=0*****/
    pstBoot->StartClusSecNo = (pstBoot->BPB_BytesPerSec - 1)
    / pstBoot->BPB_BytesPerSec
    + pstBoot->BPB_RsvdSecCnt
    + pstBoot->BPB_NumFATs * pstBoot->BPB_FATSz32
    - FSTOOL_COMM_CLUSTER_FIRST * pstBoot->BPB_SecPerClus;

    /***get the totle Clusters number***/
    pstBoot->NumSectors = pstBoot->BPB_TotSec32;
    pstBoot->NumClusters = (pstBoot->BPB_TotSec32 - pstBoot->StartClusSecNo) / pstBoot->BPB_SecPerClus;
    pstBoot->NumFatEntries = (pstBoot->BPB_FATSz32 * pstBoot->BPB_BytesPerSec) / 4;
    if (pstBoot->NumFatEntries < pstBoot->NumClusters)
    {
        FSTOOL_COMM_LOG_ERROR("The FAT size too small, %u entries won't fit %u sectors\n",
               pstBoot->NumClusters, pstBoot->BPB_FATSz32);
        return FSCK_FSBOOTERR;
    }

    pstBoot->ClusterSize = pstBoot->BPB_BytesPerSec * pstBoot->BPB_SecPerClus;
    //pstBoot->FATClnShutBit =0xff;
    pstBoot->NumFiles = 1;
    pstBoot->NumFree = 0;

    FSTOOL_COMM_LOG_INFO("->ClusterSize :%d \n",pstBoot->ClusterSize);
    FSTOOL_COMM_LOG_INFO("->NumFatEntries :%d \n",pstBoot->NumFatEntries);
    FSTOOL_COMM_LOG_INFO("->NumClusters :%d \n",pstBoot->NumClusters);
    FSTOOL_COMM_LOG_INFO("->NumSectors :%d \n",pstBoot->NumSectors);

    return FSCK_FSOK;
}

int FSCK_BOOT_IsDirty(FSCK_BOOT_SECTOR_S* pstBoot)
{
    if (pstBoot->BPB_Reserved1)
    {
        FSTOOL_COMM_LOG_WARN("File system is dirty due to BPB_Reserved1!\n");
        return 1;
    }

    return 0;
}

u_int FSCK_BOOT_CleanDirtyFlag(int fd)
{
    u_char dirtyFlag = 0x00;
    if (FSCK_FSOK != FSTOOL_IO_Write(fd, 65, 1, &dirtyFlag))
    {
        FSTOOL_COMM_LOG_ERROR("Unable to write dirtyFalg to DBR[65]!! \n");
        return FSCK_FSFATAL|FSCK_WRITERR;
    }

    return FSCK_FSOK;
}

u_int FSCK_BOOT_UpdateFSInfo(FSCK_BOOT_SECTOR_S* pstBoot, int fd, cl_t lastAllocatedClu, u_char* fsinfo_sector)
{
    int neetUpdate = 0;

    FSTOOL_COMM_LOG_DEBUG("Verify Filesystem information\n");

    if (pstBoot->FSI_FreeClCnt != pstBoot->NumFree)
    {
        FSTOOL_COMM_LOG_WARN("Free space in FSInfo block (%d) not correct (%d)\n",pstBoot->FSI_FreeClCnt, pstBoot->NumFree);
        pstBoot->FSI_FreeClCnt = pstBoot->NumFree;
        neetUpdate = 1;
    }

    if (lastAllocatedClu > pstBoot->NumClusters)
    {
        lastAllocatedClu = FSTOOL_COMM_CLUSTER_FIRST;
    }

    if (pstBoot->FSI_LastCl != lastAllocatedClu)
    {
        FSTOOL_COMM_LOG_WARN("last allocated cluster in FSInfo block(%d) is not correct(%d)\n", pstBoot->FSI_LastCl, lastAllocatedClu);
        pstBoot->FSI_LastCl = lastAllocatedClu;
        neetUpdate = 1;
    }

    if(fsinfo_sector == NULL)
    {
        FSTOOL_COMM_LOG_ERROR("FsInfo malloc failed\n");
        return FSCK_FSFATAL;
    }

    if (neetUpdate)
    {
        fsinfo_sector[FSCK_BOOT_FSI_FREEC_OFFSET] = (u_char)pstBoot->FSI_FreeClCnt;
        fsinfo_sector[FSCK_BOOT_FSI_FREEC_OFFSET+1] = (u_char)(pstBoot->FSI_FreeClCnt >> 8);
        fsinfo_sector[FSCK_BOOT_FSI_FREEC_OFFSET+2] = (u_char)(pstBoot->FSI_FreeClCnt >> 16);
        fsinfo_sector[FSCK_BOOT_FSI_FREEC_OFFSET+3] = (u_char)(pstBoot->FSI_FreeClCnt >> 24);
        fsinfo_sector[FSCK_BOOT_FSI_NXT_OFFSET] = (u_char)pstBoot->FSI_LastCl;
        fsinfo_sector[FSCK_BOOT_FSI_NXT_OFFSET+1] = (u_char)(pstBoot->FSI_LastCl >> 8);
        fsinfo_sector[FSCK_BOOT_FSI_NXT_OFFSET+2] = (u_char)(pstBoot->FSI_LastCl >> 16);
        fsinfo_sector[FSCK_BOOT_FSI_NXT_OFFSET+3] = (u_char)(pstBoot->FSI_LastCl >> 24);

        if (FSCK_FSOK != FSTOOL_IO_Write(fd, pstBoot->BPB_FSInfo, 1, fsinfo_sector))
        {
            FSTOOL_COMM_LOG_ERROR("Unable to write FSInfo!! \n");
            return FSCK_FSFATAL|FSCK_WRITERR;
        }
    }

    return FSCK_FSOK;
}

